Skip to content

Conversation

@hvitved
Copy link
Contributor

@hvitved hvitved commented Oct 24, 2025

We don't actually check the regexp, but assume that it does proper sanitization.

@github-actions github-actions bot added the Java label Oct 24, 2025
@hvitved hvitved marked this pull request as ready for review October 24, 2025 07:34
@hvitved hvitved requested review from a team as code owners October 24, 2025 07:34
Copilot AI review requested due to automatic review settings October 24, 2025 07:34
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR treats calls to String.matches(regexp) as sanitizers for server-side request forgery (SSRF) vulnerabilities in Java code. The implementation assumes that any regex pattern used with .matches() provides adequate sanitization without validating the actual regex pattern.

Key Changes:

  • Added a new sanitizer class MatchesSanitizer that treats any call to a matches() method as a barrier against request forgery
  • Added test cases demonstrating both inline and extracted validation patterns using .matches()
  • Added a change note documenting this new behavior

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
java/ql/lib/semmle/code/java/security/RequestForgery.qll Implements the new MatchesSanitizer class and isMatchesSanitizer predicate to treat .matches() calls as sanitizers
java/ql/test/query-tests/security/CWE-918/SanitizationTests.java Adds test cases showing sanitization via regex validation with .matches()
java/ql/src/change-notes/2025-10-24-request-forgery-matches-sanitizer.md Documents the new sanitizer behavior in the change notes
misc/scripts/create-change-note.py Updates comment to reference change categories documentation

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

}

/**
* A qualifier in a call to a `.matches()` method that is a sanitizer for URL redirects.
Copy link

Copilot AI Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation mentions 'URL redirects' but this sanitizer is for request forgery (SSRF), not URL redirection. The comment should refer to 'request forgery' or 'SSRF' to match the actual usage context.

Copilot uses AI. Check for mistakes.
}

/**
* A qualifier in a call to `.matches()` that is a sanitizer for URL redirects.
Copy link

Copilot AI Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation mentions 'URL redirects' but should refer to 'request forgery' or 'SSRF' to accurately describe the sanitizer's purpose in this context.

Copilot uses AI. Check for mistakes.
}

private void validate(String s) {
if (!s.matches("[a-zA-Z0-9/_-]+")) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allowing / here sounds like playing with fire, so let's not do that.

Suggested change
if (!s.matches("[a-zA-Z0-9/_-]+")) {
if (!s.matches("[a-zA-Z0-9_-]+")) {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I happily took what Copilot gave me ;-)

Copy link
Contributor

@aschackmull aschackmull left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor comment, otherwise LGTM. I checked that at least Go also uses regex as an SSRF sanitizer, so there's established precedence for at least one other language.

Comment on lines 123 to 137
// GOOD: sanitisation by regexp validation
String safeUri10 = "https://example.com/";
String param10 = request.getParameter("uri10");
if (param10.matches("[a-zA-Z0-9/_-]+")) {
safeUri10 = safeUri10 + param10;
}
HttpRequest r10 = HttpRequest.newBuilder(new URI(safeUri10)).build();
client.send(r10, null);


String param11 = request.getParameter("uri11");
validate(param11);
String safeUri11 = "https://example.com/" + param11;
HttpRequest r11 = HttpRequest.newBuilder(new URI(safeUri11)).build();
client.send(r11, null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait a minute, are these two test cases even testing the regex sanitizer? If I've understood the existing sanitizers correctly, then the concatenation with a fixed url ending in / like "https://example.com/" + param11 is itself a sanitizer.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two test cases need to replace the fixed-string url "https://example.com/" with something that isn't analyzable as a potential url-prefix by the StringPrefixes library. Maybe just get the string from a collection field that we pretend is initialized or something.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch; the first test is actually ok, since safeUri10 is not a constant, but I'll update that as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have verified that the updated tests both result in alerts when the sanitizer is disabled.

@hvitved
Copy link
Contributor Author

hvitved commented Oct 24, 2025

DCA shows that this alert in webgoat is now no longer reported, which I believe is a good thing?

@aschackmull
Copy link
Contributor

DCA shows that this alert in webgoat is now no longer reported, which I believe is a good thing?

Webgoat is somewhat silly in terms of testing QL - in this case this is the exact spot that the WebGoat SSRF exercise is supposed to succeed - i.e. the ssrf vulnerability is exploited. But of course WebGoat is written to still be in control of the exercise, so SSRF is only allowed to target one very specific website, and hence from our point of view the input is actually sanitized. So yeah.

@hvitved hvitved merged commit 32f21d6 into github:main Oct 24, 2025
17 checks passed
@hvitved hvitved deleted the java/request-forgery-matches-sanitizer branch October 24, 2025 12:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants